#include "fs.hpp"
#include <stdio.h>
#include "common.hpp"
#include "error.h"
#include "errno.h"

extern "C"{
#include "dir.h"
#include "bootsect.h"
}

bool FS::setDevice(Device& device)
{
	//umount current if we have one
	if(volume){
		log("FS: oops we have mounted volume \n");
		int res = ntfs_umount(volume, NO);
		if( res != 0 ){
			log("FS: umount failed \n");
			return false;
		}
		volume = 0;
	}

	//try mount
	const char *dev = device.file.toLocal8Bit();
	volume = ntfs_mount(dev, MS_RDONLY);
	if(!volume){
		//log("FS: '%s' ntfs_mount failed\n", dev);
		perror("FS: ntfs mount failed");
		return false;
	}

	return true;
}

void FS::setFilters(QDir::Filters filters)
{
	m_filters = filters;
}

struct FilldirContext{
	QStringList *sl;
	QDir::Filters filters;
};

static int filldir(void *context, const ntfschar *name, 
				   const int name_len, const int name_type, const s64,
				   const MFT_REF, const unsigned dt_type)
{
	QString qname = QString::fromUtf16(name, name_len);
	if( qname == "." || qname == ".." || name_type == FILE_NAME_DOS )
		return 0;
	
	FilldirContext *fc = (FilldirContext*)context;
	int pass = false;
	pass +=  (fc->filters & QDir::Dirs) && (dt_type == NTFS_DT_DIR);
	pass += (fc->filters & QDir::Files) && (dt_type == NTFS_DT_REG);
	if(pass)
		*fc->sl << qname;
	
	return 0;
}

QStringList FS::entryList(QString path, QDir::Filters filters)
{
	QStringList sl = QStringList();
	
	if( !volume )
		return sl;

	//lookup inode
	ntfs_inode *ni = ntfs_pathname_to_inode(volume, NULL, path.toLocal8Bit());
	if(!ni){
		return sl;
	}

	//readdir
	FilldirContext fc = { &sl, filters };
	s64 pos = 0;
	ntfs_readdir(ni, &pos, &fc, filldir);

	ntfs_inode_close(ni);
	
	return sl;
}

bool FS::recognize(Device& dev)
{
	//for now we can only recognize a NTFS fs
	//read first sector and pass it to ntfs_boot_sector_is_ntfs
	do{
		QFile file(dev.file);
		if ( !file.open(QIODevice::ReadOnly) ){
			log("FS: '%s' open failed\n", (const char*)dev.file.toLocal8Bit());
			break;
		}
		
		NTFS_BOOT_SECTOR bb;
		if( !file.read((char*)&bb, sizeof(bb)) ){
			log("FS: read '%s' failed\n", (const char*)dev.file.toLocal8Bit());
			break;
		}
		
		if( !ntfs_boot_sector_is_ntfs(&bb) ){
			log("FS: '%s' is not ntfs\n", (const char*)dev.file.toLocal8Bit());
			break;
		}
		
		dev.fsType = "NTFS";
		
		return true;
	}while(0);

	return false;
}

int FS::read(QString qpath, char* buf, size_t size, size_t offset)
{
	log("FS:read file '%s', size %ld, offset %ld \n", qpath.toAscii().data(), size, offset);
	
	ntfs_volume *vol;
	ntfs_inode *ni = NULL;
	ntfs_attr *na = NULL;
	int res, total = 0;

	ntfschar *stream_name = AT_UNNAMED;
	int stream_name_len = 0;

	/*
	stream_name_len = ntfs_fuse_parse_path(org_path, &path, &stream_name);
	if (stream_name_len < 0)
		return stream_name_len;
	*/

	vol = volume;
	ni = ntfs_pathname_to_inode(vol, NULL, qpath.toLocal8Bit().data());
	if (!ni) {
		res = -errno;
		goto exit;
	}

	na = ntfs_attr_open(ni, AT_DATA, stream_name, stream_name_len);
	if (!na) {
		res = -errno;
		goto exit;
	}

	if (offset + size > na->data_size)
		size = na->data_size - offset;
	while (size) {
		res = ntfs_attr_pread(na, offset, size, buf);
		if (res < (s64)size)
			log("FS: ntfs_attr_pread returned less bytes than requested.\n");
		if (res <= 0) {
			res = -errno;
			goto exit;
		}
		size -= res;
		offset += res;
		total += res;
	}
	res = total;

exit:
	if (na)
		ntfs_attr_close(na);
	if (ni && ntfs_inode_close(ni))
		log("FS: Failed to close inode\n");	
	/*
	if (stream_name_len)
		free(stream_name);
	*/

	return res;
}

int FS::stat(QString path, FileInfo* buf)
{
	ntfs_inode *ni;
	ntfs_attr *na;

	ni = ntfs_pathname_to_inode(volume, NULL, path.toLocal8Bit().data());
	if(!ni)
		return -1;

	buf->dtime = QDateTime::fromTime_t(ni->last_access_time);
	buf->ctime = QDateTime::fromTime_t(ni->last_mft_change_time);
	buf->mtime = QDateTime::fromTime_t(ni->last_data_change_time);

	if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) {
		na = ntfs_attr_open(ni, AT_INDEX_ALLOCATION, NTFS_INDEX_I30, 4);
		if (na) {
			buf->size = na->data_size;
			buf->blocks = na->allocated_size >> 9;
			ntfs_attr_close(na);
		}
	}
	else{
		buf->size = ni->data_size;
		buf->blocks = (ni->allocated_size + 511) >> 9;
	}
	
	return 0;
}
